package tcp

import (
	"encoding/binary"
	"fmt"
	"tym/modbus"
)

// TcpProtocolDataUnit modbus 包
// TsId 事务 2字节
// Protocol 协议 2字节
// DataLength 数据长度 2字节
// SlaveId 站号 1字节
// FunctionCode 功能码 1字节
// Address 寄存器地址 2字节
// Complex 复合字段, 读： 数量、 写：值
type TcpProtocolDataUnit struct {
	TsId       uint16
	Protocol   uint16
	DataLength uint16
	SlaveId    uint8

	FunctionCode uint8
	Address      uint16
	Complex      uint16
}

func Pack(tsId uint16, slaveId uint8, functionCode uint8, address uint16, complex uint16) modbus.Packager {
	return &TcpProtocolDataUnit{
		TsId:         tsId,
		Protocol:     0,
		DataLength:   6,
		SlaveId:      slaveId,
		FunctionCode: functionCode,
		Address:      address,
		Complex:      complex,
	}
}

func (mb *TcpProtocolDataUnit) Encode() []byte {
	requestData := []byte{
		byte(mb.TsId >> 8),
		byte(mb.TsId & 0xFF),
		byte(mb.Protocol >> 8),
		byte(mb.Protocol & 0xFF),
		byte(mb.DataLength >> 8),
		byte(mb.DataLength & 0xFF),
		mb.SlaveId,
		mb.FunctionCode,
		byte(mb.Address >> 8),
		byte(mb.Address & 0xFF),
		byte(mb.Complex >> 8),
		byte(mb.Complex & 0xFF),
	}
	return requestData
}

// Decode
func (mb *TcpProtocolDataUnit) Decode(res []byte) []uint16 {

	// 判断功能号
	var data []byte
	switch mb.FunctionCode {
	case modbus.FuncCodeReadHoldingRegisters:
		// modbus tcp
		// 事务标识   2 字节
		// 协议标识   2 字节
		// 数据长度   2 字节
		// 站号标识   1 字节
		// 功能号标识  1 字节
		// 数据大小标识 1 字节
		// [9:] 数据内容
		data = res[9:]
	case modbus.FuncCodeWriteHoldingRegisters:
		// 06 原路判断
		data = res[8:]
	}

	values := make([]uint16, len(data)/2)
	for i := 0; i < len(values); i++ {
		values[i] = uint16(data[2*i])<<8 | uint16(data[2*i+1])
	}

	return values
}

func (mb *TcpProtocolDataUnit) Verify(req []byte, res []byte) error {

	// txId 校验
	reqTxId := binary.BigEndian.Uint16(req)
	resTxId := binary.BigEndian.Uint16(res)
	if reqTxId != resTxId {
		return fmt.Errorf("modbus tcp: txid %v not res %v", reqTxId, resTxId)
	}

	// Protocol id
	reqProId := binary.BigEndian.Uint16(req[2:])
	resProId := binary.BigEndian.Uint16(res[2:])
	if reqProId != resProId {
		return fmt.Errorf("modbus tcp: ProId %v not res %v", reqProId, resProId)
	}

	return nil
}
